home *** CD-ROM | disk | FTP | other *** search
- Subject: v08i057: Directory hiearchy scanner
- Newsgroups: mod.sources
- Approved: mirror!rs
-
- Submitted by: Alan Silverstein <seismo!hplabs!hpfcla!hpfcdt!ajs>
- Mod.sources: Volume 8, Issue 57
- Archive-name: hier
-
- hier(1) is yet another way to view a directory hierarchy. It's
- analogous to ls -R, but presents the data in a new fashion especially
- useful for novice users or display on a wall. Now, with the plethora of
- similar tools available, what's special about this one?
-
- - I wrote it after four *years* of consideration and using similar tools.
- - I interacted with an "end user" who knew what he wanted.
- - The code is carefully crafted, commented, and tested. (But not yet
- widely ported, I fear.)
-
- So give it a try, and see if you like it.
-
- Also note! This package includes a "sorted ftw()", which is a major
- and non-trivial library routine you'll love if you are familiar with
- ftw(3) (file tree walk), but wish you could get sorted results.
-
- Alan Silverstein, Hewlett-Packard Systems Software Operation, Fort Collins,
- Colorado; {ihnp4 | hplabs}!hpfcla!ajs; 303-229-3053; (lat-long on request :-)
-
- [ I ported SFTW to BSD. This included writing (most of?) <ftw.h>
- and a couple of real minor tweaks. I started on HIER, but gave up
- at the first bug. A public-domain manpage for sftw would be real
- nice. -r$ ]
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line,
- # then unpack it by saving it in a file and typing "sh file".
- # If all goes well, you will see the message "End of shell archive."
- # Contents: Makefile Makefile.bsd ftw.h hier.1 hier.c sftw.c
- PATH=/bin:/usr/bin:/usr/ucb; export PATH
- echo shar: extracting "'Makefile'" '(324 characters)'
- if test -f 'Makefile' ; then
- echo shar: will not over-write existing file "'Makefile'"
- else
- sed 's/^X//' >Makefile <<'@//E*O*F Makefile//'
- X# Makefile for hier(1), needed to link with sftw().
- X
- Xhier: hier.c sftw.o
- X cc -v -O -s -o hier hier.c sftw.o
- X
- Xsftw.o: sftw.c
- X cc -v -O -c sftw.c
- X
- Xdebug: hier.c sftw.c
- X cc -v -g -o hier hier.c sftw.c
- X rm -f hier.o sftw.o # since they're useless.
- X
- Xtest: sftw.c
- X cc -v -O -o sftw sftw.c -DTEST
- X
- Xclean:
- X rm -f *.o hier sftw core
- @//E*O*F Makefile//
- if test 324 -ne "`wc -c <'Makefile'`"; then
- echo shar: error transmitting "'Makefile'" '(should have been 324 characters)'
- fi
- fi # end of overwriting check
- echo shar: extracting "'Makefile.bsd'" '(298 characters)'
- if test -f 'Makefile.bsd' ; then
- echo shar: will not over-write existing file "'Makefile.bsd'"
- else
- sed 's/^X//' >Makefile.bsd <<'@//E*O*F Makefile.bsd//'
- X# Makefile for hier(1), needed to link with sftw().
- X# This is for BSD sites. mirror!rs
- X
- XCFLAGS = -O -DBSD -I. -Dstrrchr=rindex
- XGETOPT=-lgetopt
- X
- Xhier: hier.c sftw.o
- X cc $(CFLAGS) -s -o hier hier.c sftw.o $(GETOPT)
- X
- Xtest: sftw.c
- X cc $(CFLAGS) -DTEST -o sftw sftw.c
- X
- Xclean:
- X rm -f *.o hier sftw core
- @//E*O*F Makefile.bsd//
- if test 298 -ne "`wc -c <'Makefile.bsd'`"; then
- echo shar: error transmitting "'Makefile.bsd'" '(should have been 298 characters)'
- fi
- fi # end of overwriting check
- echo shar: extracting "'ftw.h'" '(576 characters)'
- if test -f 'ftw.h' ; then
- echo shar: will not over-write existing file "'ftw.h'"
- else
- sed 's/^X//' >ftw.h <<'@//E*O*F ftw.h//'
- X/*
- X** This is a version of <ftw.h> for sites that don't have it. I just
- X** picked four values at random for the #define's that sftw needed; I
- X** doubt that this is binary-compatible with AT&T's <ftw.h>, and this
- X** may well be missing something. Oh, well, it's public-domain...
- X** -Rich $alz, mirror!rs
- X*/
- X
- X
- X/*
- X** These values are passed on to the user's function, as the third
- X** parameter.
- X*/
- X#define FTW_F 1 /* A file */
- X#define FTW_D 2 /* A directory */
- X#define FTW_DNR 3 /* A directory that couldn't be read */
- X#define FTW_NS 4 /* A stat() failure */
- @//E*O*F ftw.h//
- if test 576 -ne "`wc -c <'ftw.h'`"; then
- echo shar: error transmitting "'ftw.h'" '(should have been 576 characters)'
- fi
- fi # end of overwriting check
- echo shar: extracting "'hier.1'" '(1732 characters)'
- if test -f 'hier.1' ; then
- echo shar: will not over-write existing file "'hier.1'"
- else
- sed 's/^X//' >hier.1 <<'@//E*O*F hier.1//'
- X.TH HIER 1 "Unsupported Utility"
- X.SH NAME
- Xhier \- show filesystem hierarchy
- X.SH SYNOPSIS
- X.B hier
- X[
- X.B \-adp
- X] [
- X.BI \-c \0columns
- X] [
- X.BI \-i \0indent
- X]
- X.br
- X[
- X.I directories...
- X]
- X.ad b
- X.SH DESCRIPTION
- XThis command shows you a filesystem hierarchy in a useful, indented way.
- XAt each level files are sorted in two groups:
- Xnon-directory files,
- Xthen directories (recursing into each one).
- XIt examines the named
- X.IR directories ,
- Xor by default the present working directory.
- X.PP
- XOptions are:
- X.TP
- X.B \-a
- XAll: include directories and files whose names start with ".".
- X.TP
- X.B \-d
- XShow directories only; skip other types of files.
- X.TP
- X.B \-p
- XPrint filenames packed onto lines, not aligned in columns.
- X.TP
- X.B \-c
- XSet width of display for showing multiple filenames on a line
- X(or use the COLUMNS environment variable).
- XThe default is 80 columns.
- X.TP
- X.B \-i
- XSet indentation (number of blanks) per hierarchy level.
- XThe default is 4 spaces per level.
- X.SH EXAMPLES
- X.TP
- Xhier
- X.br
- XShow all non-"." files, recursively,
- Xin and under the current directory.
- X.TP
- Xhier -apc 40 /etc
- XShow all directories and files,
- Xincluding any whose filenames start with ".",
- Xin a format 40 columns wide,
- Xand with filenames packed into lines,
- Xunder directory "/etc".
- X.SH SEE ALSO
- Xls(1), sftw(3) (does not exist (yet))
- X.SH DIAGNOSTICS
- XIf a file is not stat-able,
- Xor a directory is not readable,
- Xthe filename is printed on a line to itself,
- Xlike a directory (sorted with directory names),
- Xwith an appropriate message following.
- X.SH BUGS
- XUnlike
- X.IR ls (1),
- Xit sorts files across lines rather than down columns.
- XFixing this would be non-trivial.
- X.PP
- XAlso, due to the behavior of
- X.IR sftw (3)
- X(like
- X.IR ftw (3)),
- Xit never lists "." and ".." files, even with the
- X.B \-a
- Xoption.
- @//E*O*F hier.1//
- if test 1732 -ne "`wc -c <'hier.1'`"; then
- echo shar: error transmitting "'hier.1'" '(should have been 1732 characters)'
- fi
- fi # end of overwriting check
- echo shar: extracting "'hier.c'" '(8409 characters)'
- if test -f 'hier.c' ; then
- echo shar: will not over-write existing file "'hier.c'"
- else
- sed 's/^X//' >hier.c <<'@//E*O*F hier.c//'
- X/*
- X * Show file system hierarchy.
- X *
- X * Usage: see below.
- X *
- X * Unlike ls(1), it sorts files across lines rather than down columns.
- X * Fixing this would be non-trivial, involving saving filenames until it
- X * was time to dump them.
- X *
- X * Also, due to the behavior of sftw() (like ftw()), it never lists "." and
- X * ".." files.
- X *
- X * Warning: If you use ftw() instead of sftw(), -a option will stop working.
- X *
- X * Warning: If you use ftw() instead of sftw(), a bug will appear. This is
- X * because two calls in a row from ftw() to OneFile() may be made for ordinary
- X * files, where the second is in a directory a level above the first one.
- X * OneFile() would have to check to see if each ordinary file's path is
- X * different than the previous one's, indicating a change of directory level.
- X */
- X
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#include <stdio.h>
- X#include <ftw.h>
- X#ifdef BSD
- X#include <sys/dir.h> /* for DIRSIZ */
- X#include <strings.h>
- X#else
- X#include <sys/ndir.h> /* for DIRSIZ */
- X#include <string.h>
- X#endif /* BSD */
- X
- X
- X/*********************************************************************
- X * MISCELLANEOUS GLOBAL VALUES:
- X */
- X
- X#define PROC /* null; easy to find procs */
- X#define FALSE 0
- X#define TRUE 1
- X#define CHNULL ('\0')
- X#define CPNULL ((char *) NULL)
- X#define REG register
- X
- Xchar *usage[] = {
- X "usage: %s [-adp] [-c columns] [-i indent] [directories...]",
- X "-a include directories and files whose names start with \".\"",
- X "-d show directories only",
- X "-p print filenames packed onto lines, not aligned",
- X "-c set width of display (or use COLUMNS env variable; default = 80)",
- X "-i set indentation per level; default = 4",
- X "Does current directory by default.",
- X CPNULL,
- X};
- X
- Xchar *myname; /* how program was invoked */
- Xint aflag = FALSE; /* -a (all files) option */
- Xint dflag = FALSE; /* -d (directories) option */
- Xint pflag = FALSE; /* -p (packed filenames) option */
- Xint columns = 0; /* from -c option or env */
- Xint indent = 0; /* from -i option or default */
- X
- X#define COLUMNS 80 /* width of display */
- X#define INDENT 4 /* per directory level */
- X
- Xint startlen; /* of current arg (filename) */
- Xint nextcol = 0; /* column in output line */
- X
- X
- X/************************************************************************
- X * M A I N
- X *
- X * Initialize, then call sftw() for each given filename after clearing
- X * global startlen to indicate a new starting file. When done, if global
- X * nextcol != 0 (in the middle of an output line), finish the last line.
- X */
- X
- XPROC main (argc, argv)
- X int argc;
- X char **argv;
- X{
- Xextern int optind; /* from getopt() */
- Xextern char *optarg; /* from getopt() */
- XREG int option; /* option "letter" */
- X char *argdef = "."; /* default argument */
- X char *colstr; /* from environment */
- X
- X char *getenv();
- X int OneFile();
- X
- X/* #define DEPTH (_NFILE - 5) /* for ftw(), but not sftw() */
- X
- X/*
- X * PARSE ARGUMENTS:
- X */
- X
- X myname = *argv;
- X
- X while ((option = getopt (argc, argv, "adpc:i:")) != EOF)
- X {
- X switch (option)
- X {
- X case 'a': aflag = TRUE; break;
- X case 'd': dflag = TRUE; break;
- X case 'p': pflag = TRUE; break;
- X case 'c': columns = atoi (optarg); break;
- X case 'i': indent = atoi (optarg); break;
- X default: Usage();
- X }
- X }
- X
- X if (dflag && pflag)
- X Error ("-d and -p don't make sense together");
- X
- X/*
- X * FINISH INITIALIZING:
- X */
- X
- X if ((columns == 0) /* no value given */
- X && (((colstr = getenv ("COLUMNS")) == CPNULL) /* undefined */
- X || ((columns = atoi (colstr)) == 0))) /* defined null or zero */
- X {
- X columns = COLUMNS; /* use default */
- X }
- X
- X if (indent == 0) /* no value given */
- X indent = INDENT; /* use default */
- X
- X argc -= optind; /* skip options */
- X argv += optind;
- X
- X if (argc == 0) /* no filenames given */
- X {
- X argc = 1;
- X argv = & argdef; /* use default */
- X }
- X
- X/*
- X * WALK EACH FILE TREE:
- X */
- X
- X while (argc-- > 0)
- X {
- X startlen = 0;
- X
- X if (sftw (*argv, OneFile, aflag))
- X Error ("file tree walk failed for file \"%s\"", *argv);
- X
- X argv++;
- X }
- X
- X if (nextcol) /* last line not finished */
- X putchar ('\n');
- X
- X exit (0);
- X
- X} /* main */
- X
- X
- X/************************************************************************
- X * O N E F I L E
- X *
- X * Called from sftw() to handle (print) one file, given a filename (starting
- X * file plus sub-file part) and ftw() file type. Always returns zero (all
- X * is well, keep going).
- X *
- X * It's messy because of the need to print multiple non-directory basenames
- X * on one line. Uses global startlen to save time figuring depth beyond
- X * starting file. If currently zero, this is the starting file; print the
- X * fullname, on a line alone, with no indent.
- X *
- X * Use globals startlen, indent, columns, and nextcol.
- X */
- X
- XPROC int OneFile (filename, statp, type)
- X char *filename; /* name */
- X struct stat *statp; /* info, unused */
- X int type; /* ftw() type */
- X{
- XREG char *basename; /* part of filename */
- X
- X/*
- X * PRINTING FORMATS (matching ftw() types):
- X */
- X
- Xstatic char *FMT_D = "%s/\n";
- Xstatic char *FMT_DNR = "%s/ (not readable)\n";
- Xstatic char *FMT_NS = "%s (not stat'able)\n";
- Xstatic char *FMT_F1 = "%s\n"; /* for starting file */
- Xstatic char *FMT_F2 = "%-*s"; /* for sub-file */
- X
- X#ifdef BSD
- X#define FILEWIDTH MAXNAMLEN /* for FMT_F2 */
- X#else
- X#define FILEWIDTH (DIRSIZ + 1) /* for FMT_F2 */
- X#endif /* BSD */
- XREG int filewidth = FILEWIDTH; /* if ! pflag */
- X
- X#define NEWLINE { putchar ('\n'); nextcol = 0; } /* for speed and clarity */
- X
- X/*
- X * OPTIONALLY IGNORE NON-DIRECTORY (even if named as an input argument):
- X */
- X
- X if (dflag && (type == FTW_F))
- X return (0);
- X
- X/*
- X * HANDLE STARTING FILE:
- X */
- X
- X if (startlen == 0)
- X {
- X startlen = strlen (filename); /* set for later */
- X
- X if (nextcol) /* end previous line */
- X NEWLINE; /* sets nextcol == 0 */
- X
- X printf ((type == FTW_D) ? FMT_D :
- X (type == FTW_DNR) ? FMT_DNR :
- X (type == FTW_NS) ? FMT_NS : FMT_F1, filename);
- X
- X return (0);
- X }
- X
- X/*
- X * SET BASENAME FOR ALL OTHER TYPES:
- X */
- X
- X basename = ((basename = strrchr (filename, '/')) == CPNULL) ?
- X filename : (basename + 1); /* past "/" if any */
- X
- X/*
- X * HANDLE NON-DIRECTORY SUB-FILE (print multiple per line):
- X */
- X
- X if (type == FTW_F)
- X {
- X if (pflag) /* else use preset value */
- X filewidth = strlen (basename) + 1;
- X
- X if (nextcol && (nextcol + filewidth >= columns)) /* overflow */
- X NEWLINE; /* sets nextcol == 0 */
- X
- X if (nextcol == 0) /* start new line with indent */
- X nextcol = PrintIndent (filename);
- X
- X printf (FMT_F2, filewidth, basename);
- X nextcol += filewidth;
- X return (0);
- X }
- X
- X/*
- X * HANDLE DIRECTORY OR OTHER SUB-FILE (print on line by itself):
- X */
- X
- X if (nextcol) /* end previous line */
- X NEWLINE; /* sets nextcol == 0 */
- X
- X PrintIndent (filename);
- X
- X printf ((type == FTW_D) ? FMT_D :
- X (type == FTW_DNR) ? FMT_DNR : FMT_NS, basename);
- X
- X return (0);
- X
- X} /* OneFile */
- X
- X
- X/************************************************************************
- X * P R I N T I N D E N T
- X *
- X * Given a filename and globals startlen and indent, print the total
- X * indentation needed before the name, which is indent times the number of
- X * slashes past startlen (which should be >= 1). Return the indent value.
- X */
- X
- XPROC int PrintIndent (filename)
- XREG char *filename;
- X{
- XREG int depth = 0;
- XREG int totind;
- X int retval;
- X
- X filename += startlen; /* start of sub-part */
- X
- X while (*filename != CHNULL)
- X if (*filename++ == '/')
- X depth++;
- X
- X retval = totind = indent * depth;
- X
- X while (totind-- > 0)
- X putchar (' ');
- X
- X return (retval);
- X
- X} /* PrintIndent */
- X
- X
- X/************************************************************************
- X * U S A G E
- X *
- X * Print usage messages (char *usage[]) to stderr and exit nonzero.
- X * Each message is followed by a newline.
- X */
- X
- XPROC Usage()
- X{
- XREG int which = 0; /* current line */
- X
- X while (usage [which] != CPNULL)
- X {
- X fprintf (stderr, usage [which++], myname);
- X putc ('\n', stderr);
- X }
- X
- X exit (1);
- X
- X} /* Usage */
- X
- X
- X/************************************************************************
- X * E R R O R
- X *
- X * Print an error message to stderr and exit nonzero. Message is preceded
- X * by "<myname>: " using global char *myname, and followed by a newline.
- X */
- X
- X/* VARARGS */
- XPROC Error (message, arg1, arg2, arg3, arg4)
- X char *message;
- X long arg1, arg2, arg3, arg4;
- X{
- X fprintf (stderr, "%s: ", myname);
- X fprintf (stderr, message, arg1, arg2, arg3, arg4);
- X putc ('\n', stderr);
- X
- X exit (1);
- X
- X} /* Error */
- @//E*O*F hier.c//
- if test 8409 -ne "`wc -c <'hier.c'`"; then
- echo shar: error transmitting "'hier.c'" '(should have been 8409 characters)'
- fi
- fi # end of overwriting check
- echo shar: extracting "'sftw.c'" '(14313 characters)'
- if test -f 'sftw.c' ; then
- echo shar: will not over-write existing file "'sftw.c'"
- else
- sed 's/^X//' >sftw.c <<'@//E*O*F sftw.c//'
- X/*
- X * Sorted file tree walk (library routine).
- X *
- X * Identical (in theory) to ftw(3), except:
- X *
- X * - Calls user's fn() with the files sorted alphabetically (per strcmp(3))
- X * in two groups: All non-directories first, followed by directories (with
- X * the descendents of each directory after the directory). Non-stat'able
- X * files and non-readable directories are included in the second group.
- X *
- X * - Doesn't keep one file open for each level of recursion, so it doesn't
- X * need a depth argument (which actually affects file opens/closes, NOT
- X * maximum search depth).
- X *
- X * - Uses a lot more malloc space.
- X *
- X * - Supports an additional argument which tells it to include all files
- X * and directories, including those whose names start with "." (except that
- X * the given filename is always included, regardless of the flag, like
- X * ls(1)). The caller could implement this, but not very efficiently.
- X *
- X * Like ftw(), it ignores "." and ".." files, even with the all flag.
- X *
- X * For convenience, form of call is:
- X *
- X * #include <ftw.h>
- X *
- X * int sftw (path, fn, allfiles)
- X * char *path;
- X * int (*fn)();
- X * int allfiles;
- X *
- X * Form of fn() is:
- X *
- X * int fn (name, statp, type)
- X * char *name;
- X * struct stat *statp;
- X * int type;
- X *
- X * See ftw(3) for more information.
- X *
- X * Compile with -DTEST to get a runnable test version that walks from "."
- X * and tells types, permissions, and filenames passed to fn().
- X */
- X
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#include <ftw.h>
- X#ifdef BSD
- X#include <sys/dir.h>
- X#else
- X#include <ndir.h>
- X#endif /* BSD */
- X
- Xstatic char *malloc(), *strcpy();
- Xstatic void free();
- X
- X
- X/*********************************************************************
- X * MISCELLANEOUS GLOBAL VALUES:
- X */
- X
- X#define PROC /* null; easy to find procs */
- X#define FALSE 0
- X#define TRUE 1
- X#define CHNULL ('\0')
- X#define CPNULL ((char *) NULL)
- X#define REG register
- X
- X/* built-up filename for passing to the user program; hope it's big enough */
- Xstatic char filename [1000];
- X
- Xstatic unsigned short euid, egid; /* only get them once */
- X
- X
- X
- X/************************************************************************
- X * FILE DATA STRUCTURE:
- X *
- X * A contiguous array of pointers is used for sorting, built after knowing
- X * how many directory entries there are to sort. Each entry points to a
- X * struct filedata which holds information for one directory entry.
- X */
- X
- Xtypedef struct filedata *fdpt;
- Xtypedef struct filedata **fdppt;
- X
- Xstruct filedata {
- X char *name; /* in malloc'd space */
- X int type; /* see ftw.h */
- X struct stat statbuf; /* from stat(2) */
- X};
- X
- X
- X/************************************************************************
- X * FILE AND STRING DATA BLOCKS:
- X *
- X * Since a directory may grow arbitrarily as it's read, there's no way to
- X * know in advance how big it is. And it's necessary to return all malloc'd
- X * memory. To make this possible, and to save malloc space and time, directory
- X * entry data and filenames are stored in buffers allocated a chunk a time.
- X */
- X
- X#define DBENTRIES 20 /* file entries per datablock */
- X#define STRINGENTRIES 1008 /* chars per string buffer */
- X
- Xtypedef struct datablock *dbpt;
- Xtypedef struct datablock **dbppt;
- X
- Xstruct datablock {
- X dbpt next; /* next block if any */
- X struct filedata fd [DBENTRIES]; /* the data itself */
- X};
- X
- X#define DBNULL ((dbpt) NULL)
- X#define DBSIZE (sizeof (struct datablock))
- X
- X
- Xtypedef struct stringblock *sbpt;
- Xtypedef struct stringblock **sbppt;
- X
- Xstruct stringblock {
- X sbpt next; /* next block if any */
- X char buf [STRINGENTRIES]; /* the data itself */
- X};
- X
- X#define SBNULL ((sbpt) NULL)
- X#define SBSIZE (sizeof (struct stringblock))
- X
- X
- X/************************************************************************
- X * S F T W
- X *
- X * Handle the filename given by the user. Since sftw() must stat() each
- X * file before sorting, the first (top level) file must be handled specially,
- X * not as part of re-entrant code. (Think about it...)
- X */
- X
- XPROC int sftw (path, UserFunc, allfiles)
- X char *path;
- X int (*UserFunc)();
- X int allfiles;
- X{
- X struct stat statbuf; /* from first file */
- X int type; /* of first file */
- X int retval; /* return by UserFunc() */
- X unsigned short geteuid(), getegid();
- X
- X euid = geteuid(); /* initialize values */
- X egid = getegid();
- X
- X/*
- X * HANDLE INITIAL FILE:
- X */
- X
- X type = GetType (path, & statbuf);
- X
- X if (retval = UserFunc (path, & statbuf, type)) /* it's unhappy */
- X return (retval);
- X
- X if (type != FTW_D) /* we're done */
- X return (0);
- X
- X/*
- X * WORK ON A READABLE DIRECTORY:
- X */
- X
- X strcpy (filename, path); /* now we can append to it */
- X strcat (filename, "/"); /* prepare for additions */
- X
- X return (DoDirectory (UserFunc, allfiles));
- X
- X} /* sftw */
- X
- X
- X/************************************************************************
- X * D O D I R E C T O R Y
- X *
- X * Given UserFunc(), all files flag, and global filename (directory path) where
- X * to start and on which to build complete pathnames, read the directory, sort
- X * filenames, and call UserFunc() for each file in the directory. This routine
- X * calls itself to recurse, after each directory name is passed to UserFunc().
- X * Because it reads and saves a directory's contents in order to sort them, it
- X * does not keep any files open while recursing, just lots of memory.
- X *
- X * Free all memory from this level before returning, even in case of error.
- X * Return -1 in case of error, or the value from UserFunc() if non-zero.
- X */
- X
- XPROC static int DoDirectory (UserFunc, allfiles)
- X int (*UserFunc)();
- X int allfiles; /* include ".*" files? */
- X{
- X dbpt dbhead = DBNULL; /* first datablock ptr */
- X sbpt sbhead = SBNULL; /* first stringblock ptr */
- X
- X fdppt fdphead; /* filedata list to sort */
- XREG fdppt fdpcurr; /* current list pointer */
- XREG fdpt fdcurr; /* current entry pointer */
- XREG int files; /* number in directory */
- X
- X int retval; /* copy of return value */
- X
- X/* pointer into filename where to append basenames */
- XREG char *basename = filename + strlen (filename);
- X
- X int FDCmp();
- X void qsort();
- X
- X#define RETURN(value) { FreeBlocks (dbhead, sbhead); return (value); }
- X
- X/*
- X * READ DIRECTORY:
- X */
- X
- X if ((files = ReadDirectory (& dbhead, & sbhead, allfiles)) < 0)
- X RETURN (-1);
- X
- X/*
- X * BUILD AND SORT POINTERS TO FILES:
- X *
- X * Get a big chunk of contiguous memory for the pointers, then set them up.
- X * Afterwards, filedata entries will be accessed via the pointers.
- X */
- X
- X if ((fdphead = (fdppt) malloc (files * sizeof (fdpt))) == (fdppt) NULL)
- X RETURN (-1);
- X
- X#undef RETURN
- X#define RETURN(value) { FreeBlocks (dbhead, sbhead); \
- X free ((char *) fdphead); return (value); }
- X
- X SetFDList (fdphead, fdphead + files, dbhead);
- X qsort ((char *) fdphead, (unsigned) files, sizeof (fdpt), FDCmp);
- X
- X/*
- X * TRAVERSE FILES USING SORTED POINTERS:
- X *
- X * Append each file's basename to the current path in global filename,
- X * overlaying whatever basename was there before, and pass it to UserFunc().
- X */
- X
- X fdpcurr = fdphead;
- X
- X while (files-- > 0)
- X {
- X strcpy (basename, (fdcurr = (*fdpcurr++)) -> name);
- X
- X if (retval = UserFunc (filename, & (fdcurr -> statbuf),
- X fdcurr -> type)) /* it's unhappy */
- X {
- X RETURN (retval);
- X }
- X
- X/*
- X * RECURSE FOR A DIRECTORY:
- X */
- X
- X if ((fdcurr -> type) == FTW_D)
- X {
- X strcat (basename, "/"); /* for next level */
- X
- X if (retval = DoDirectory (UserFunc, allfiles))
- X RETURN (retval);
- X }
- X }
- X
- X RETURN (0);
- X
- X} /* DoDirectory */
- X
- X
- X/************************************************************************
- X * R E A D D I R E C T O R Y
- X *
- X * Given pointers to datablock and stringblock chain head pointers, all files
- X * flag, and global filename (name of a readable directory) on which to build
- X * complete pathnames, open, read, and close a directory, saving name, stat,
- X * and type information on each file in a chain of datablocks and stringblocks,
- X * and setting the chain head pointers. Return the number of filenames read
- X * and saved (>= 0), or -1 in case of error, but always close the directory.
- X */
- X
- XPROC static int ReadDirectory (dbheadp, sbheadp, allfiles)
- X dbppt dbheadp; /* start of chain */
- X sbppt sbheadp; /* start of chain */
- X int allfiles; /* include ".*" files? */
- X{
- XREG DIR *dirp; /* for reading directory */
- XREG struct direct *entp; /* directory entry */
- XREG char *name; /* fast copy of filename */
- X
- XREG dbpt dbcurr; /* current datablock ptr */
- X dbppt dbnextp = dbheadp; /* next datablock ptr ptr */
- XREG int dbentry = DBENTRIES; /* current entry in block */
- X
- XREG fdpt fdcurr; /* current filedata entry */
- X
- XREG sbpt sbcurr; /* current stringblock ptr */
- X sbppt sbnextp = sbheadp; /* next stringblock ptr ptr */
- XREG char *end = ""; /* last + 1 of block */
- XREG char *start = end; /* next free place */
- X
- X/* pointer into filename where to append basenames */
- XREG char *basename = filename + strlen (filename);
- XREG int files = 0; /* files read and saved */
- X
- X#undef RETURN
- X#define RETURN(value) { closedir (dirp); return (value); }
- X
- X/*
- X * OPEN AND READ DIRECTORY:
- X */
- X
- X if ((dirp = opendir (filename)) == (DIR *) NULL)
- X return (-1); /* hope errno is set */
- X
- X /* now be sure to use the RETURN() macro */
- X
- X while ((entp = readdir (dirp)) != (struct direct *) NULL)
- X {
- X
- X/*
- X * OPTIONALLY SKIP ".*" FILES:
- X *
- X * Always skip "." and ".." files, like ftw().
- X */
- X
- X if ((* (name = entp -> d_name) == '.') /* fast check */
- X && ((! allfiles)
- X || (name [1] == CHNULL)
- X || ((name [1] == '.') && (name [2] == CHNULL))))
- X {
- X continue;
- X }
- X
- X files++;
- X
- X/*
- X * GET A NEW DATABLOCK; APPEND TO CHAIN:
- X */
- X
- X if (dbentry >= DBENTRIES) /* block is full */
- X {
- X if ((dbcurr = *dbnextp = (dbpt) malloc (DBSIZE)) == DBNULL)
- X RETURN (-1);
- X
- X * (dbnextp = & (dbcurr -> next)) = DBNULL;
- X dbentry = 0;
- X }
- X
- X/*
- X * GET A NEW STRINGBLOCK; APPEND TO CHAIN:
- X *
- X * Yes, we may abandon some unused space in the previous block... Hope that
- X * STRINGENTRIES is much larger than the average directory entry name size.
- X */
- X
- X if ((entp -> d_namlen) + 1 > (end - start))
- X {
- X if ((sbcurr = *sbnextp = (sbpt) malloc (SBSIZE)) == SBNULL)
- X RETURN (-1);
- X
- X * (sbnextp = & (sbcurr -> next)) = SBNULL;
- X end = (start = (sbcurr -> buf)) + STRINGENTRIES;
- X }
- X
- X/*
- X * SAVE INFORMATION ON ONE FILE:
- X *
- X * Append each file's basename to the current path in global filename,
- X * overlaying whatever basename was there before, and pass it to GetType().
- X */
- X
- X fdcurr = (dbcurr -> fd) + (dbentry++); /* quick pointer */
- X
- X strcpy (((fdcurr -> name) = start), name);
- X start += (entp -> d_namlen) + 1;
- X
- X strcpy (basename, name);
- X (fdcurr -> type) = GetType (filename, & (fdcurr -> statbuf));
- X
- X } /* while */
- X
- X RETURN (files);
- X
- X} /* ReadDirectory */
- X
- X
- X/************************************************************************
- X * G E T T Y P E
- X *
- X * Given a filename and a pointer to a stat structure, stat() the file into the
- X * structure and return an appropriate ftw() type. Since directories are not
- X * opened for reading until much later, after sorting, determine readability
- X * now using euid, egid, and st_mode. Can't use access(2) because it checks
- X * real ids, not effective (sigh).
- X */
- X
- XPROC static int GetType (name, statp)
- X char *name;
- X struct stat *statp;
- X{
- X#define UREAD (S_IREAD) /* read permission bits for user, group, other */
- X#define GREAD (S_IREAD >> 3)
- X#define OREAD (S_IREAD >> 6)
- X
- X if (stat (name, statp) < 0)
- X return (FTW_NS); /* not stat'able */
- X
- X if (((statp -> st_mode) & S_IFMT) != S_IFDIR)
- X return (FTW_F); /* not directory */
- X
- X /* pick appropriate permission bit, then see if it's set */
- X
- X return (
- X ( ((statp -> st_uid) == euid) ? ((statp -> st_mode) & UREAD) :
- X ((statp -> st_gid) == egid) ? ((statp -> st_mode) & GREAD) :
- X ((statp -> st_mode) & OREAD) ) ?
- X FTW_D : FTW_DNR);
- X
- X} /* GetType */
- X
- X
- X/************************************************************************
- X * S E T F D L I S T
- X *
- X * Given pointers to the current (head) and end + 1 of an array of
- X * uninitialized struct filedata pointers, and a current (head) struct
- X * datablock pointer, fill in the pointers in the array to point to the
- X * filedata entries in the datablock chain.
- X */
- X
- XPROC static SetFDList (fdpcurr, fdpend, dbcurr)
- XREG fdppt fdpcurr; /* start at head */
- XREG fdppt fdpend; /* last + 1 */
- XREG dbpt dbcurr; /* start at head */
- X{
- XREG int dbentry; /* current index */
- X
- X while (TRUE) /* until return */
- X {
- X for (dbentry = 0; dbentry < DBENTRIES; dbentry++) /* one block */
- X {
- X if (fdpcurr >= fdpend) /* no more */
- X return;
- X
- X *fdpcurr++ = (dbcurr -> fd) + dbentry;
- X }
- X
- X dbcurr = dbcurr -> next;
- X }
- X
- X /* never get here */
- X
- X} /* SetFDList */
- X
- X
- X/************************************************************************
- X * F D C M P
- X *
- X * Given two pointers to pointers to filedata entries, compare the entries
- X * and return -1, 0, or 1 for how they relate. "Normal" files (FTW_F)
- X * are always lower than other types, then names are compared with strcmp().
- X */
- X
- XPROC static int FDCmp (fdpp1, fdpp2)
- X fdppt fdpp1, fdpp2;
- X{
- XREG int type1 = (*fdpp1) -> type;
- XREG int type2 = (*fdpp2) -> type;
- X
- X return (((type1 == FTW_F) && (type2 != FTW_F)) ? -1 :
- X ((type1 != FTW_F) && (type2 == FTW_F)) ? 1 :
- X strcmp ((*fdpp1) -> name, (*fdpp2) -> name));
- X
- X} /* FDCmp */
- X
- X
- X/************************************************************************
- X * F R E E B L O C K S
- X *
- X * Given pointers to heads of datablock and stringblock chains, free the
- X * malloc'd memory in the chains.
- X */
- X
- XPROC static FreeBlocks (dbhead, sbhead)
- XREG dbpt dbhead;
- XREG sbpt sbhead;
- X{
- XREG dbpt dbtemp;
- XREG sbpt sbtemp;
- X
- X while (dbhead != DBNULL)
- X {
- X dbtemp = dbhead -> next;
- X free ((char *) dbhead);
- X dbhead = dbtemp;
- X }
- X
- X while (sbhead != SBNULL)
- X {
- X sbtemp = sbhead -> next;
- X free ((char *) sbhead);
- X sbhead = sbtemp;
- X }
- X
- X} /* FreeBlocks */
- X
- X
- X#ifdef TEST
- X
- X/************************************************************************
- X * TEST HARNESS:
- X */
- X
- XPROC int fn (name, statp, type)
- X char *name;
- X struct stat *statp;
- X int type;
- X{
- X printf ("%-3s %06o \"%s\"\n",
- X (type == FTW_F) ? "F" : (type == FTW_D) ? "D" :
- X (type == FTW_DNR) ? "DNR" : (type == FTW_NS) ? "NS" : "?",
- X statp -> st_mode, name);
- X
- X return (0);
- X}
- X
- XPROC main()
- X{
- X printf ("sftw returns %d\n", sftw (".", fn));
- X}
- X
- X#endif TEST
- @//E*O*F sftw.c//
- if test 14313 -ne "`wc -c <'sftw.c'`"; then
- echo shar: error transmitting "'sftw.c'" '(should have been 14313 characters)'
- fi
- fi # end of overwriting check
- echo shar: "End of shell archive."
- exit 0
-